 /*******************************************************************************
  * Copyright (c) 2000, 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.texteditor;

 import java.util.ArrayList ;
 import java.util.Collections ;
 import java.util.Comparator ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.widgets.Menu;
 import org.eclipse.swt.widgets.Shell;

 import org.eclipse.jface.viewers.IDoubleClickListener;

 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.IDocument;
 import org.eclipse.jface.text.IInformationControl;
 import org.eclipse.jface.text.IInformationControlCreator;
 import org.eclipse.jface.text.IInformationControlCreatorExtension;
 import org.eclipse.jface.text.ITextViewerExtension5;
 import org.eclipse.jface.text.Position;
 import org.eclipse.jface.text.TextViewer;
 import org.eclipse.jface.text.source.Annotation;
 import org.eclipse.jface.text.source.CompositeRuler;
 import org.eclipse.jface.text.source.IAnnotationAccess;
 import org.eclipse.jface.text.source.IAnnotationAccessExtension;
 import org.eclipse.jface.text.source.IAnnotationHover;
 import org.eclipse.jface.text.source.IAnnotationHoverExtension;
 import org.eclipse.jface.text.source.IAnnotationModel;
 import org.eclipse.jface.text.source.ILineRange;
 import org.eclipse.jface.text.source.ISourceViewer;
 import org.eclipse.jface.text.source.IVerticalRulerListener;
 import org.eclipse.jface.text.source.LineRange;
 import org.eclipse.jface.text.source.VerticalRulerEvent;

 import org.eclipse.ui.internal.texteditor.AnnotationExpansionControl.AnnotationHoverInput;

 /**
  * @since 3.0
  */
 public class AnnotationExpandHover implements IAnnotationHover, IAnnotationHoverExtension {

     private class InformationControlCreator implements IInformationControlCreator, IInformationControlCreatorExtension {

         /*
          * @see org.eclipse.jface.text.IInformationControlCreator#createInformationControl(org.eclipse.swt.widgets.Shell)
          */
         public IInformationControl createInformationControl(Shell parent) {
             return new AnnotationExpansionControl(parent, SWT.NONE, fAnnotationAccess);
         }

         /*
          * @see org.eclipse.jface.text.IInformationControlCreatorExtension#canReuse(org.eclipse.jface.text.IInformationControl)
          */
         public boolean canReuse(IInformationControl control) {
             return control instanceof AnnotationExpansionControl;
         }

         /*
          * @see org.eclipse.jface.text.IInformationControlCreatorExtension#canReplace(org.eclipse.jface.text.IInformationControlCreator)
          */
         public boolean canReplace(IInformationControlCreator creator) {
             return creator == this;
         }
     }

     private class VerticalRulerListener implements IVerticalRulerListener {

         /*
          * @see org.eclipse.jface.text.source.IVerticalRulerListener#annotationSelected(org.eclipse.jface.text.source.VerticalRulerEvent)
          */
         public void annotationSelected(VerticalRulerEvent event) {
             fCompositeRuler.fireAnnotationSelected(event);
         }

         /*
          * @see org.eclipse.jface.text.source.IVerticalRulerListener#annotationDefaultSelected(org.eclipse.jface.text.source.VerticalRulerEvent)
          */
         public void annotationDefaultSelected(VerticalRulerEvent event) {
             fCompositeRuler.fireAnnotationDefaultSelected(event);
         }

         /*
          * @see org.eclipse.jface.text.source.IVerticalRulerListener#annotationContextMenuAboutToShow(org.eclipse.jface.text.source.VerticalRulerEvent, org.eclipse.swt.widgets.Menu)
          */
         public void annotationContextMenuAboutToShow(VerticalRulerEvent event, Menu menu) {
             fCompositeRuler.fireAnnotationContextMenuAboutToShow(event, menu);
         }
     }


     private final IInformationControlCreator fgCreator= new InformationControlCreator();
     protected final IVerticalRulerListener fgListener= new VerticalRulerListener();
     protected CompositeRuler fCompositeRuler;
     protected IDoubleClickListener fDblClickListener;
     protected IAnnotationAccess fAnnotationAccess;

     /**
      * Creates a new hover instance.
      *
      * @param ruler
      * @param access
      * @param doubleClickListener
      */
     public AnnotationExpandHover(CompositeRuler ruler, IAnnotationAccess access, IDoubleClickListener doubleClickListener) {
         fCompositeRuler= ruler;
         fAnnotationAccess= access;
         fDblClickListener= doubleClickListener;
     }

     /*
      * @see org.eclipse.jface.text.source.IAnnotationHover#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer, int)
      */
     public String getHoverInfo(ISourceViewer sourceViewer, int line) {
         // we don't have any sensible return value as text
 return null;
     }

     protected Object getHoverInfoForLine(ISourceViewer viewer, int line) {
         IAnnotationModel model= viewer.getAnnotationModel();
         IDocument document= viewer.getDocument();

         if (model == null)
             return null;

         List exact= new ArrayList ();
         HashMap messagesAtPosition= new HashMap ();

         Iterator e= model.getAnnotationIterator();
         while (e.hasNext()) {
             Annotation annotation= (Annotation) e.next();
             Position position= model.getPosition(annotation);
             if (position == null)
                 continue;

             if (compareRulerLine(position, document, line) == 1) {
                 if (isDuplicateMessage(messagesAtPosition, position, annotation.getText()))
                     continue;

                 exact.add(annotation);
             }
         }

         if (exact.size() < 1)
             return null;

         sort(exact, model);

         if (exact.size() > 0)
             setLastRulerMouseLocation(viewer, line);

         AnnotationHoverInput input= new AnnotationHoverInput();
         input.fAnnotations= (Annotation[]) exact.toArray(new Annotation[0]);
         input.fViewer= viewer;
         input.fRulerInfo= fCompositeRuler;
         input.fAnnotationListener= fgListener;
         input.fDoubleClickListener= fDblClickListener;
         input.model= model;

         return input;
     }

     protected void sort(List exact, final IAnnotationModel model) {
         class AnnotationComparator implements Comparator {

             /*
              * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
              */
             public int compare(Object o1, Object o2) {
                 Annotation a1= (Annotation) o1;
                 Annotation a2= (Annotation) o2;

                 Position p1= model.getPosition(a1);
                 Position p2= model.getPosition(a2);

                 // annotation order:
 // primary order: by position in line
 // secondary: annotation importance
 if (p1.offset == p2.offset)
                     return getOrder(a2) - getOrder(a1);
                 return p1.offset - p2.offset;
             }
         }

         Collections.sort(exact, new AnnotationComparator());

     }

     protected int getOrder(Annotation annotation) {
         if (fAnnotationAccess instanceof IAnnotationAccessExtension) {
             IAnnotationAccessExtension extension= (IAnnotationAccessExtension) fAnnotationAccess;
             return extension.getLayer(annotation);
         }
         return IAnnotationAccessExtension.DEFAULT_LAYER;
     }

     protected boolean isDuplicateMessage(Map messagesAtPosition, Position position, String message) {
         if (messagesAtPosition.containsKey(position)) {
             Object value= messagesAtPosition.get(position);
             if (message == null || message.equals(value))
                 return true;

             if (value instanceof List ) {
                 List messages= (List )value;
                 if (messages.contains(message))
                     return true;
                 messages.add(message);
             } else {
                 ArrayList messages= new ArrayList ();
                 messages.add(value);
                 messages.add(message);
                 messagesAtPosition.put(position, messages);
             }
         } else
             messagesAtPosition.put(position, message);
         return false;
     }

     protected void setLastRulerMouseLocation(ISourceViewer viewer, int line) {
         // set last mouse activity in order to get the correct context menu
 if (fCompositeRuler != null) {
             StyledText st= viewer.getTextWidget();
             if (st != null && !st.isDisposed()) {
                 if (viewer instanceof ITextViewerExtension5) {
                     int widgetLine= ((ITextViewerExtension5)viewer).modelLine2WidgetLine(line);
                     Point loc= st.getLocationAtOffset(st.getOffsetAtLine(widgetLine));
                     fCompositeRuler.setLocationOfLastMouseButtonActivity(0, loc.y);
                 } else if (viewer instanceof TextViewer) {
                     // TODO remove once TextViewer implements the extension
 int widgetLine= ((TextViewer)viewer).modelLine2WidgetLine(line);
                     Point loc= st.getLocationAtOffset(st.getOffsetAtLine(widgetLine));
                     fCompositeRuler.setLocationOfLastMouseButtonActivity(0, loc.y);
                 }
             }
         }
     }

     /**
      * Returns the distance to the ruler line.
      *
      * @param position the position
      * @param document the document
      * @param line the line number
      * @return the distance to the ruler line
      */
     protected int compareRulerLine(Position position, IDocument document, int line) {

         if (position.getOffset() > -1 && position.getLength() > -1) {
             try {
                 int firstLine= document.getLineOfOffset(position.getOffset());
                 if (line == firstLine)
                     return 1;
                 if (firstLine <= line && line <= document.getLineOfOffset(position.getOffset() + position.getLength()))
                     return 2;
             } catch (BadLocationException x) {
             }
         }

         return 0;
     }

     /*
      * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverControlCreator()
      */
     public IInformationControlCreator getHoverControlCreator() {
         return fgCreator;
     }

     /*
      * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverInfo(org.eclipse.jface.text.source.ISourceViewer, org.eclipse.jface.text.source.ILineRange, int)
      */
     public Object getHoverInfo(ISourceViewer sourceViewer, ILineRange lineRange, int visibleLines) {
         return getHoverInfoForLine(sourceViewer, lineRange.getStartLine());
     }

     /*
      * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#getHoverLineRange(org.eclipse.jface.text.source.ISourceViewer, int)
      */
     public ILineRange getHoverLineRange(ISourceViewer viewer, int lineNumber) {
         return new LineRange(lineNumber, 1);
     }

     /*
      * @see org.eclipse.jface.text.source.IAnnotationHoverExtension#canHandleMouseCursor()
      */
     public boolean canHandleMouseCursor() {
         return true;
     }
 }

